package sim32;


/**
 * @author Piotr
 * Class for the EX Stage. Performs ALU calculations.
 */
public class EXStage extends PipeStage {

	EXStage(int pipeID) {
		super(pipeID);
	}

	@Override
	int cycle(PipeStage prevStage) throws InvalidInstructionException {
		if (!stalled) {
			instr = ((IDStage)prevStage).passInstruction(pipeID);
			if (instr == null || instr.isNop()) {
				return 0;
			} else {
				instr.cycle();
				doCycle();
			}
			return 1;
		} else if (causedStall) {
			causedStall = false;
			stalled = false;
			if (instr == null || instr.isNop()) {
				return 0;
			} else {
				doCycle();
			}
			return 1;
		} else {
			if (instr != null) {
				doForwarding();
			}
			stalled = false;
			if (instr != null && !instr.isNop()) {
				return 1;
			} else {
				return 0;
			}
		}
	}

	private void doCycle() {
		doForwarding();
		switch (instr.iClass) {
		case RTYPE:
			if (instr.arg1Ready && instr.arg2Ready) {
				doRtype(instr);
			} else {
				Sim.pipe.hzc.stallPipe(this.getClass(), pipeID);
			}
			break;
		case SHIFT:
			if (instr.arg1Ready) {
				doShift(instr);
			} else {
				Sim.pipe.hzc.stallPipe(this.getClass(), pipeID);
			}
		case SHIFTV:
			if (instr.arg1Ready && instr.arg2Ready) {
				doShift(instr);
			} else {
				Sim.pipe.hzc.stallPipe(this.getClass(), pipeID);
			}
		case IMM:
			if (instr.arg1Ready && instr.arg2Ready) {
				doImm(instr);
			} else {
				Sim.pipe.hzc.stallPipe(this.getClass(), pipeID);
			}
			break;
		case MEM:
			doLoadStore(instr);
			break;
		case BRANCH:
			doBranch(instr);
		default:
			break;
		}
	}
	
	private void doBranch(Instruction instr) {
		switch (instr.iType) {
		case JALR:
			instr.setDest1Val(instr.address + 1);
			break;
		}
	}

	private void doLoadStore(Instruction instr) {
		switch (instr.iType) {
		case LOAD:
			if (instr.arg1Ready && instr.arg2Ready) {
				instr.memAddr = instr.arg1Val + instr.arg2Val;
			} else {
				Sim.pipe.hzc.stallPipe(this.getClass(), pipeID);
			}
			break;
		case STORE:
			if (instr.arg2Ready) {
				instr.memAddr = instr.arg2Val + instr.arg3Val;
			} else {
				Sim.pipe.hzc.stallPipe(this.getClass(), pipeID);
			}
			break;
		}
	}

	private void doImm(Instruction instr) {
		switch (instr.iType) {
		case LUI:
			instr.setDest1Val(instr.arg1Val << 16);
			break;
		case ADDI:
			instr.setDest1Val(instr.arg2Val + instr.arg1Val);
			break;
		case ORI:
			instr.setDest1Val(instr.arg1Val | instr.arg2Val);
		}
	}

	private void doRtype(Instruction instr) {
		switch (instr.iType) {
		case ADD:
			instr.setDest1Val(instr.arg1Val + instr.arg2Val);
			break;
		case SUB:
			instr.setDest1Val(instr.arg1Val - instr.arg2Val);
			break;
		case MUL:
			long prod = new Long((long)instr.arg1Val * instr.arg2Val);
			int lo = (int)(prod);
			int hi = (int)(prod >> 32);
			instr.setDest1Val(lo);
			instr.setDest2Val(hi);
			break;
		case AND:
			instr.setDest1Val(instr.arg1Val & instr.arg2Val);
			break;
		case OR:
			instr.setDest1Val(instr.arg1Val | instr.arg2Val);
			break;
		case NOR:
			instr.setDest1Val(~(instr.arg1Val | instr.arg2Val));
			break;
		}
	}
	
	private void doShift(Instruction instr) {
		switch (instr.iType) {
		case SLL:
			instr.setDest1Val(instr.arg1Val << instr.arg2Val);
			break;
		case SRL:
			instr.setDest1Val(instr.arg1Val >>> instr.arg2Val);
			break;
		case SRA:
			instr.setDest1Val(instr.arg1Val >> instr.arg2Val);
			break;
		case SLLV:
			instr.setDest1Val(instr.arg1Val << (instr.arg2Val&0x1F) );
			break;
		case SRLV:
			instr.setDest1Val(instr.arg1Val >>> (instr.arg2Val&0x1F));
			break;
		case SRAV:
			instr.setDest1Val(instr.arg1Val >> (instr.arg2Val&0x1F));
			break;
		}
	}
	
	private void doForwarding() {
		if (instr.arg1Reg != -1 && !instr.arg1Ready) {
			Integer i = Sim.pipe.hzc.tryForwarding(instr.arg1Reg, this.getClass(), pipeID);
			if (i != null) {
				instr.arg1Val = i;
				instr.arg1Ready = true;
			}
		}
		if (instr.arg2Reg != -1 && !instr.arg2Ready) {
			Integer i = Sim.pipe.hzc.tryForwarding(instr.arg2Reg, this.getClass(), pipeID);
			if (i != null) {
				instr.arg2Val = i;
				instr.arg2Ready = true;
			}
		}
	}
	
}
